Skip to content

Fix lazy PhysX IMU and PVA acceleration#6439

Open
AntoineRichard wants to merge 7 commits into
isaac-sim:developfrom
AntoineRichard:antoiner/fix-inertial-sensor-elapsed-time
Open

Fix lazy PhysX IMU and PVA acceleration#6439
AntoineRichard wants to merge 7 commits into
isaac-sim:developfrom
AntoineRichard:antoiner/fix-inertial-sensor-elapsed-time

Conversation

@AntoineRichard

Copy link
Copy Markdown
Collaborator

Summary

  • compute PhysX IMU and PVA finite-difference acceleration from the elapsed time between sensor samples
  • keep timestamp inputs stable inside the recorded Warp launches from Optimize PhysX IMU and PVA updates #6390
  • remove mutable scalar inverse-dt launch parameters
  • add lazy-read and nonzero-update-period regressions for IMU linear acceleration and PVA linear/angular acceleration

This PR is stacked on #6390. Until #6390 merges, its commits are included in this comparison against develop.

Root cause

IMU and PVA cached velocity only when their buffers were recomputed, but divided the next velocity delta by the most recent physics-step dt. With four lazy physics updates or an update period of four physics steps, both sensors reported 4x acceleration.

The kernels now derive a per-environment interval from timestamp minus timestamp_last_update.

Validation

  • Unmodified Optimize PhysX IMU and PVA updates #6390 base: 4 regressions failed with the expected 4x acceleration error
  • Fixed targeted regressions: 4 passed
  • Complete PhysX IMU suite: 11 passed
  • Complete PhysX PVA suite: 11 passed
  • Final pre-commit hooks: passed
  • Existing recorded-launch assertions remain enabled and pass

Performance

Benchmarking is deferred until the test machine is connected to AC power. The change removes scalar launch-parameter updates and retains the recorded-launch path; this PR makes no performance claims.

Provide a shared standalone benchmark for the PhysX IMU and PVA update paths so baseline, cached-eager, and recorded-launch latency can be compared under the same workload.
Reuse stable typed PhysX buffers and recorded Warp commands to reduce per-update Python overhead. Refresh changing masks and timesteps on the recorded commands so runtime behavior remains unchanged.
Keep the IMU and PVA benchmark with the PhysX package sensor benchmarks so backend-specific performance tools share one discoverable location.
The recorded-launch unit tests allocate directly on cuda:0 and would
error rather than skip on CPU-only runners. Gate the module on CUDA
availability and tag it for the short isaacsim CI lane.
The cached typed views and the recorded launch assume the PhysX
getters refresh pointer-stable buffers in place. Verify the pointers
on every fetch so a re-backed buffer fails loudly instead of silently
freezing the sensor data.
A recording failure only warns and falls back to eager launches, so
nothing would notice the optimization silently dying. Assert the
recorded command exists after CUDA integration runs, pass production-
shaped ProxyArray gravity in the unit fixture, and assert the PhysX
getters are still called on replay since they refresh the buffers.
Derive finite-difference acceleration from the elapsed time between sensor samples. This keeps lazy reads and nonzero update periods correct while preserving recorded Warp launches.
@github-actions github-actions Bot added bug Something isn't working isaac-lab Related to Isaac Lab team labels Jul 9, 2026
@AntoineRichard

Copy link
Copy Markdown
Collaborator Author

Newton cross-backend validation: using this PR worktree source explicitly on PYTHONPATH, the Newton IMU and PVA ten-step lazy free-fall acceleration tests both pass (2 passed). Newton is not affected by this bug because it consumes native accelerometer/body-acceleration state rather than numerically differentiating sensor samples. No Newton code changes are needed.

@greptile-apps

greptile-apps Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a 4× acceleration over-reporting bug in the PhysX IMU and PVA sensors: both sensors were computing finite-difference acceleration using the most recent physics dt even when multiple physics steps had elapsed since the last sensor read (lazy updates or a nonzero update_period). The kernels now derive a per-environment elapsed_time = timestamp - timestamp_last_update and guard against non-positive intervals, giving correct acceleration regardless of update cadence.

  • Kernel fix (imu/kernels.py, pva/kernels.py): inv_dt scalar parameter removed; kernels compute inv_dt from the timestamp arrays already available as recorded-launch inputs.
  • Recorded-launch refactor (imu.py, pva.py): PhysX typed views are cached after the first call and pointer-stability is asserted on every subsequent call; a wp.Launch command is recorded on first use and replayed on subsequent updates, with graceful fallback to eager launches on CUDA recording failure.
  • New test file (test_imu_pva_recorded_launch.py): unit tests for view caching, recorded-launch replay, and invalidation cleanup contain two bugs that will cause test failures (see inline comments).

Confidence Score: 3/5

The production sensor code (kernels, imu.py, pva.py) is correct and well-structured. The new test file ships with two concrete bugs that will prevent the tests from passing as written.

The kernel fix and recorded-launch refactor in production code are sound, but test_imu_pva_recorded_launch.py has two independent failures: _make_sensor never sets _timestamp_last_update, so every test that calls _update_buffers_impl raises AttributeError before any assertion runs; and test_sensor_invalidation_drops_cached_launch_state asserts that _invalidate_initialize_callback clears _update_inv_dt, but neither the IMU nor PVA callback touches that attribute. The real-scenario regression tests in test_imu.py and test_pva.py look correct, but the dedicated recorded-launch test suite would not pass as committed.

source/isaaclab_physx/test/sensors/test_imu_pva_recorded_launch.py needs the most attention: missing _timestamp_last_update initialisation in _make_sensor and the stale _update_inv_dt attribute in the invalidation test.

Important Files Changed

Filename Overview
source/isaaclab_physx/isaaclab_physx/sensors/imu/kernels.py Replaces scalar inv_dt parameter with timestamp_last_update array; kernel now computes elapsed_time = timestamp - timestamp_last_update per-environment and guards against non-positive intervals. Logic is correct.
source/isaaclab_physx/isaaclab_physx/sensors/pva/kernels.py Mirrors the IMU kernel change: replaces scalar inv_dt with per-environment elapsed-time computation from timestamp_last_update. Logic is correct.
source/isaaclab_physx/isaaclab_physx/sensors/imu/imu.py Adds recorded Warp launch support with pointer-stability checks, lazy typed-view caching, graceful fallback on recording failure, and proper invalidation cleanup. Production code is well-structured.
source/isaaclab_physx/isaaclab_physx/sensors/pva/pva.py Same recorded-launch refactor as imu.py applied symmetrically to PVA. Implementation mirrors IMU correctly.
source/isaaclab_physx/test/sensors/test_imu_pva_recorded_launch.py New test file has two blocking bugs: _make_sensor omits _timestamp_last_update (AttributeError on any wp.launch call) and test_sensor_invalidation_drops_cached_launch_state asserts a stale _update_inv_dt attribute that no implementation path clears.
source/isaaclab_physx/test/sensors/test_imu.py Adds test_acceleration_uses_elapsed_sensor_time covering both lazy_read and update_period modes; asserts that acceleration is 0.1/dt regardless of how many physics steps elapsed before the sensor reads.
source/isaaclab_physx/test/sensors/test_pva.py Adds matching test_acceleration_uses_elapsed_sensor_time for PVA covering both linear and angular acceleration in both lazy/update_period modes.
source/isaaclab_physx/benchmark/sensors/benchmark_imu_pva.py New benchmark measuring submission and synchronized latency of IMU/PVA update paths; accesses private _use_recorded_launch attribute to optionally disable the optimization.
source/isaaclab_physx/changelog.d/antoiner-fix-inertial-sensor-elapsed-time.rst File is empty — no changelog entry for the elapsed-time fix despite other fixes having entries.

Comments Outside Diff (1)

  1. source/isaaclab_physx/changelog.d/antoiner-fix-inertial-sensor-elapsed-time.rst, line 1 (link)

    P2 Changelog entry is empty

    The file antoiner-fix-inertial-sensor-elapsed-time.rst was added with no content. The companion file physx-imu-pva-record-launch.rst shows the expected RST structure (Fixed, ^^^^^, bullet). This entry should document the elapsed-time finite-difference fix (the root cause described in the PR description).

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "Fix lazy IMU and PVA acceleration" | Re-trigger Greptile

Comment on lines +74 to +141
def _make_sensor(sensor_type: str, use_recorded_launch: bool = True):
"""Create a two-environment IMU or PVA without a USD scene."""
device = "cuda:0"
transforms_torch = torch.tensor(
[
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0],
],
dtype=torch.float32,
device=device,
)
velocities_torch = torch.tensor(
[
[1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
],
dtype=torch.float32,
device=device,
)
coms_torch = torch.tensor(
[
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0],
],
dtype=torch.float32,
)
transforms = wp.from_torch(transforms_torch.contiguous()).view(wp.transformf)
velocities = wp.from_torch(velocities_torch.contiguous()).view(wp.spatial_vectorf)
coms = wp.from_torch(coms_torch.contiguous()).view(wp.transformf)
rigid_view = _FakeRigidView(transforms, velocities, coms)

sensor_cls = Imu if sensor_type == "imu" else Pva
sensor = sensor_cls.__new__(sensor_cls)
sensor.cfg = SimpleNamespace(prim_path=f"/World/{sensor_type.upper()}")
sensor._device = device
sensor._num_envs = 2
sensor._view = rigid_view
sensor._dt = 0.5
sensor._timestamp = wp.ones(2, dtype=wp.float32, device=device)
sensor._offset_pos_b = wp.zeros(2, dtype=wp.vec3f, device=device)
sensor._offset_quat_b = wp.array(
[wp.quatf(0.0, 0.0, 0.0, 1.0), wp.quatf(0.0, 0.0, 0.0, 1.0)], dtype=wp.quatf, device=device
)
sensor._prev_lin_vel_w = wp.zeros(2, dtype=wp.vec3f, device=device)
sensor._coms_buffer = wp.zeros(2, dtype=wp.transformf, device=device)
sensor._raw_transforms = None
sensor._raw_velocities = None
sensor._raw_coms = None
sensor._update_cmd = None
sensor._update_env_mask = None
sensor._update_inv_dt = None
sensor._use_recorded_launch = use_recorded_launch
sensor._initialize_handle = None
sensor._invalidate_initialize_handle = None
sensor._prim_deletion_handle = None

if sensor_type == "imu":
sensor._data = ImuData()
sensor._data.create_buffers(num_envs=2, device=device)
sensor._gravity_bias_w = wp.zeros(2, dtype=wp.vec3f, device=device)
else:
sensor._data = PvaData()
sensor._data.create_buffers(num_envs=2, device=device)
sensor._prev_ang_vel_w = wp.zeros(2, dtype=wp.vec3f, device=device)
sensor.GRAVITY_VEC_W = ProxyArray(wp.zeros(2, dtype=wp.vec3f, device=device))

env_mask = wp.ones(2, dtype=wp.bool, device=device)
return sensor, rigid_view, velocities_torch, env_mask

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing _timestamp_last_update causes AttributeError in all kernel-launching tests

_make_sensor creates sensors via __new__, bypassing __init__ and _setup_impl. sensor_base.py (line 258) initialises _timestamp_last_update = wp.zeros_like(self._timestamp) only through the normal constructor path — so the fake sensor never gets this attribute. Every test that eventually calls _launch_update passes self._timestamp_last_update to wp.launch, raising AttributeError and failing.

Beyond the crash, the expected values in test_sensor_records_and_replays_changed_runtime_inputs were calibrated for the old scalar inv_dt = 1.0 / self._dt path: sensor._dt = 0.5inv_dt = 2.0acc = 2.0. With the new kernel computing inv_dt from timestamp - timestamp_last_update, timestamp=1.0 and timestamp_last_update=0.0 (zeros) gives inv_dt = 1.0 and acc = 1.0, not 2.0. The sensor._dt = 0.25 assignment on line 175 is now a no-op that no longer changes the kernel's inv_dt.

_make_sensor should add sensor._timestamp_last_update at an appropriate value (e.g., wp.full(2, 0.5, dtype=wp.float32, device=device) to preserve inv_dt = 2.0 and keep the existing assertions) and the stale sensor._dt assignments should be either removed or replaced with _timestamp/_timestamp_last_update adjustments that actually control the computed inv_dt.

Comment on lines +214 to +234
def test_sensor_invalidation_drops_cached_launch_state(monkeypatch, sensor_type):
"""Physics invalidation should release cached PhysX views and the recorded command."""
sensor, _, _, _ = _make_sensor(sensor_type)
sensor._raw_transforms = object()
sensor._raw_velocities = object()
sensor._raw_coms = object()
sensor._update_cmd = object()
sensor._update_env_mask = object()
sensor._update_inv_dt = 1.0
base_cls = BaseImu if sensor_type == "imu" else BasePva
monkeypatch.setattr(base_cls, "_invalidate_initialize_callback", lambda self, event: None)

sensor._invalidate_initialize_callback(None)

assert sensor._view is None
assert sensor._raw_transforms is None
assert sensor._raw_velocities is None
assert sensor._raw_coms is None
assert sensor._update_cmd is None
assert sensor._update_env_mask is None
assert sensor._update_inv_dt is None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 _update_inv_dt is never cleared by _invalidate_initialize_callback, so the final assertion will always fail

The test sets sensor._update_inv_dt = 1.0 and then asserts sensor._update_inv_dt is None after _invalidate_initialize_callback runs. But neither Imu._invalidate_initialize_callback nor Pva._invalidate_initialize_callback touches this attribute — the implementations reset _raw_transforms, _raw_velocities, _raw_coms, _update_cmd, and _update_env_mask, but not _update_inv_dt. The attribute appears to be a leftover from an earlier design where inv_dt was stored as a mutable launch parameter; the final implementation dropped it in favour of kernel-side timestamp arithmetic.

Either add self._update_inv_dt = None to both _invalidate_initialize_callback implementations, or (if the attribute is truly dead) remove the _update_inv_dt assignments and assertion from the test entirely.

@AntoineRichard AntoineRichard added this to the Isaac Lab 3.0 GA milestone Jul 10, 2026
@AntoineRichard AntoineRichard moved this to In review in Isaac Lab Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working isaac-lab Related to Isaac Lab team

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

2 participants